{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 9 Improving performance Exercises Solutions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 1\n",
    "\n",
    "- Reload the IMDB data keeping only the first 20000 most common words\n",
    "- pad the reviews to a shorter length (eg. 70 or 80), this time make sure you keep the first part of the review if it's longer than the maximum length\n",
    "- re run the model (remember to set max_features correctly)\n",
    "- does it train faster this time?\n",
    "- do you get a better performance?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from keras.datasets import imdb\n",
    "from keras.preprocessing.sequence import pad_sequences\n",
    "from keras.models import Sequential\n",
    "from keras.layers import Embedding, LSTM, Dense"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "max_features = 20000\n",
    "skip_top = 200"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "(X_train, y_train), (X_test, y_test) = imdb.load_data('/tmp/imdb.npz',\n",
    "                                                      num_words=max_features,\n",
    "                                                      start_char=1,\n",
    "                                                      oov_char=2,\n",
    "                                                      index_from=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "maxlen = 80"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "X_train_pad = pad_sequences(X_train, maxlen=maxlen, truncating='post')\n",
    "X_test_pad = pad_sequences(X_test, maxlen=maxlen, truncating='post')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "model = Sequential()\n",
    "model.add(Embedding(max_features, 128))\n",
    "model.add(LSTM(64, dropout=0.2, recurrent_dropout=0.2))\n",
    "model.add(Dense(1, activation='sigmoid'))\n",
    "\n",
    "model.compile(loss='binary_crossentropy',\n",
    "              optimizer='adam',\n",
    "              metrics=['accuracy'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.fit(X_train_pad, y_train,\n",
    "          batch_size=32,\n",
    "          epochs=2,\n",
    "          validation_split=0.3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "score, acc = model.evaluate(X_test_pad, y_test)\n",
    "print('Test score:', score)\n",
    "print('Test accuracy:', acc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 2\n",
    "\n",
    "- Reload the digits data as above\n",
    "- define a function repeated_training_reg_dropout that adds regularization and dropout to a fully connected network\n",
    "- compare the performance with/witouth dropout and regularization like we did for batch normalization\n",
    "- do you get a better performance?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from sklearn.datasets import load_digits\n",
    "from keras.utils import to_categorical\n",
    "from sklearn.model_selection import train_test_split\n",
    "from keras.layers import Dropout\n",
    "import keras.backend as K"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "digits = load_digits()\n",
    "X, y = digits.data, digits.target\n",
    "y_cat = to_categorical(y)\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y_cat, test_size=0.3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def repeated_training_reg_dropout(X_train,\n",
    "                                  y_train,\n",
    "                                  X_test,\n",
    "                                  y_test,\n",
    "                                  units=512,\n",
    "                                  activation='sigmoid',\n",
    "                                  optimizer='sgd',\n",
    "                                  do_dropout=False,\n",
    "                                  rate=0.3,\n",
    "                                  kernel_regularizer='l2',\n",
    "                                  epochs=10,\n",
    "                                  repeats=3):\n",
    "    histories = []\n",
    "    \n",
    "    for repeat in range(repeats):\n",
    "        K.clear_session()\n",
    "\n",
    "        model = Sequential()\n",
    "        \n",
    "        # first fully connected layer\n",
    "        model.add(Dense(units,\n",
    "                        input_shape=X_train.shape[1:],\n",
    "                        kernel_initializer='normal',\n",
    "                        kernel_regularizer=kernel_regularizer,\n",
    "                        activation=activation))\n",
    "        if do_dropout:\n",
    "            model.add(Dropout(rate))\n",
    "\n",
    "        # second fully connected layer\n",
    "        model.add(Dense(units,\n",
    "                        kernel_initializer='normal',\n",
    "                        kernel_regularizer=kernel_regularizer,\n",
    "                        activation=activation))\n",
    "        if do_dropout:\n",
    "            model.add(Dropout(rate))\n",
    "\n",
    "        # third fully connected layer\n",
    "        model.add(Dense(units,\n",
    "                        kernel_initializer='normal',\n",
    "                        kernel_regularizer=kernel_regularizer,\n",
    "                        activation=activation))\n",
    "        if do_dropout:\n",
    "            model.add(Dropout(rate))\n",
    "\n",
    "        # output layer\n",
    "        model.add(Dense(10, activation='softmax'))\n",
    "        \n",
    "        model.compile(optimizer,\n",
    "                      'categorical_crossentropy',\n",
    "                      metrics=['accuracy'])\n",
    "\n",
    "        h = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=epochs, verbose=0)\n",
    "        histories.append([h.history['acc'], h.history['val_acc']])\n",
    "        print(repeat, end=' ')\n",
    "\n",
    "    histories = np.array(histories)\n",
    "    \n",
    "    # calculate mean and standard deviation across repeats:\n",
    "    mean_acc = histories.mean(axis=0)\n",
    "    std_acc = histories.std(axis=0)\n",
    "    print()\n",
    "    \n",
    "    return mean_acc[0], std_acc[0], mean_acc[1], std_acc[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "mean_acc, std_acc, mean_acc_val, std_acc_val = repeated_training_reg_dropout(X_train,\n",
    "                                                                             y_train,\n",
    "                                                                             X_test,\n",
    "                                                                             y_test,\n",
    "                                                                             do_dropout=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "mean_acc_do, std_acc_do, mean_acc_val_do, std_acc_val_do = repeated_training_reg_dropout(X_train,\n",
    "                                                                                         y_train,\n",
    "                                                                                         X_test,\n",
    "                                                                                         y_test,\n",
    "                                                                                         do_dropout=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def plot_mean_std(m, s):\n",
    "    plt.plot(m)\n",
    "    plt.fill_between(range(len(m)), m-s, m+s, alpha=0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "plot_mean_std(mean_acc, std_acc)\n",
    "plot_mean_std(mean_acc_val, std_acc_val)\n",
    "plot_mean_std(mean_acc_do, std_acc_do)\n",
    "plot_mean_std(mean_acc_val_do, std_acc_val_do)\n",
    "plt.ylim(0, 1.01)\n",
    "plt.title(\"Dropout and Regularization Accuracy\")\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend(['Train', 'Test', 'Train with Dropout and Regularization', 'Test with Dropout and Regularization'], loc='best')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 3\n",
    "\n",
    "This is a very long and complex exercise, that should give you an idea of a real world scenario. Feel free to look at the solution if you feel lost. Also, feel free to run this on Floyd with a GPU, in which case you don't need to download the data.\n",
    "\n",
    "If you are running this locally, download and unpack the male/female pictures from [here](https://www.dropbox.com/s/nov493om2jmh2gp/male_female.tgz?dl=0). These images and labels were obtained from [Crowdflower](https://www.crowdflower.com/data-for-everyone/).\n",
    "\n",
    "Your goal is to build an image classifier that will recognize the gender of a person from pictures.\n",
    "\n",
    "- Have a look at the directory structure and inspect a couple of pictures\n",
    "- Design a model that will take a color image of size 64x64 as input and return a binary output (female=0/male=1)\n",
    "- Feel free to introduce any regularization technique in your model (Dropout, Batch Normalization, Weight Regularization)\n",
    "- Compile your model with an optimizer of your choice\n",
    "- Using `ImageDataGenerator`, define a train generator that will augment your images with some geometric transformations. Feel free to choose the parameters that make sense to you.\n",
    "- Define also a test generator, whose only purpose is to rescale the pixels by 1./255\n",
    "- use the function `flow_from_directory` to generate batches from the train and test folders. Make sure you set the `target_size` to 64x64.\n",
    "- Use the `model.fit_generator` function to fit the model on the batches generated from the ImageDataGenerator. Since you are streaming and augmenting the data in real time you will have to decide how many batches make an epoch and how many epochs you want to run\n",
    "- Train your model (you should get to at least 85% accuracy)\n",
    "- Once you are satisfied with your training, check a few of the misclassified pictures. Are those sensible errors?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# if you are running this on floydhub.com, uncomment and excute the next line and skip the next cell\n",
    "# data_path = /input"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# If you are running this locally\n",
    "# uncomment the next 3 lines to download, extract and set the data path:\n",
    "# !wget https://www.dropbox.com/s/nov493om2jmh2gp/male_female.tgz?dl=0 -O male_female.tgz\n",
    "# !tar -xzvf male_female.tgz\n",
    "# data_path = '../data/male_female/'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from keras.layers import Conv2D\n",
    "from keras.layers import MaxPooling2D\n",
    "from keras.layers import Flatten\n",
    "from keras.layers import BatchNormalization\n",
    "from itertools import islice\n",
    "from keras.preprocessing.image import ImageDataGenerator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "K.clear_session()\n",
    "\n",
    "model = Sequential()\n",
    "model.add(Conv2D(32, (3, 3), input_shape = (64, 64, 3), activation = 'relu'))\n",
    "model.add(MaxPooling2D(pool_size = (2, 2)))\n",
    "model.add(BatchNormalization())\n",
    "\n",
    "model.add(Conv2D(64, (3, 3), activation = 'relu'))\n",
    "model.add(MaxPooling2D(pool_size = (2, 2)))\n",
    "model.add(BatchNormalization())\n",
    "\n",
    "model.add(Conv2D(64, (3, 3), activation = 'relu'))\n",
    "model.add(MaxPooling2D(pool_size = (2, 2)))\n",
    "model.add(BatchNormalization())\n",
    "\n",
    "model.add(Flatten())\n",
    "\n",
    "model.add(Dense(128, activation = 'relu'))\n",
    "model.add(Dense(1, activation = 'sigmoid'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "model.compile(optimizer = 'adam',\n",
    "              loss = 'binary_crossentropy',\n",
    "              metrics = ['accuracy'])\n",
    "\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "train_gen = ImageDataGenerator(rescale = 1./255,\n",
    "                               width_shift_range=0.1,\n",
    "                               height_shift_range=0.1,\n",
    "                               rotation_range = 10,\n",
    "                               shear_range = 0.2,\n",
    "                               zoom_range = 0.2,\n",
    "                               horizontal_flip = True)\n",
    "\n",
    "test_gen = ImageDataGenerator(rescale = 1./255)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "train = train_gen.flow_from_directory(data_path + '/train',\n",
    "                                      target_size = (64, 64),\n",
    "                                      batch_size = 16,\n",
    "                                      class_mode = 'binary')\n",
    "\n",
    "test = test_gen.flow_from_directory(data_path + '/test',\n",
    "                                    target_size = (64, 64),\n",
    "                                    batch_size = 16,\n",
    "                                    class_mode = 'binary')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "model.fit_generator(train,\n",
    "                    steps_per_epoch = 800,\n",
    "                    epochs = 200,\n",
    "                    validation_data = test,\n",
    "                    validation_steps = 200)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "X_test = []\n",
    "y_test = []\n",
    "for ts in islice(test, 50):\n",
    "    X_test.append(ts[0])\n",
    "    y_test.append(ts[1])\n",
    "\n",
    "X_test = np.concatenate(X_test)\n",
    "y_test = np.concatenate(y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "y_pred = model.predict_classes(X_test).ravel()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.argwhere(y_test != y_pred).ravel()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "plt.imshow(X_test[14])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
